import os
import shutil
import lib.component
import sqlalchemy

from model import DeclarativeBase
from _tag import Tag
from _image_tag import ImageTag
from _image_host import ImageHost

__all__ = ['Image']


class Image(DeclarativeBase):
    """
    The image information.

    The attribute will be populated if Post object is provided during creation.

    Attributes:
        - id: An integer represent the primary key.
        - status: A string indicating the status whether queued, downloaded,
            archived or deleted. Default is Queue.
        - extension: A string(4) of image type.
        - width: An integer of image width size.
        - height: An integer of image height size.
        - size: An integer of image file size.
        - md5: A string of md5 representation.
    """

    STATUS_QUEUED = 'Queued'
    """The file is in download queue"""

    STATUS_DOWNLOADED = 'Downloaded'
    """The image file is exists in images folder"""

    STATUS_ARCHIVED = 'Archived'
    """Downloaded but the file is not available for viewing."""

    STATUS_DELETED = 'Deleted'
    """Downloaded but rejected by quality control."""

    __tablename__ = 'images'

    id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
    """
    The primary key.

    @type: int
    """

    status = sqlalchemy.Column(sqlalchemy.String(16), index=True)
    """
    The status of image file.

    Valid value is:
        - Queued: In queue download
        - Downloaded: Image is viewable
        - Archived: Downloaded but not available for viewing
        - Deleted: It's simply awful and worthless

    @type: str
    """

    extension = sqlalchemy.Column(sqlalchemy.String(4))
    """
    Image extension maximum 4 characters.

    @type: str(4)
    """

    width = sqlalchemy.Column(sqlalchemy.Integer)
    """
    Image width.

    @type: int
    """

    height = sqlalchemy.Column(sqlalchemy.Integer)
    """
    Image height.

    @type: int
    """

    size = sqlalchemy.Column(sqlalchemy.Integer)
    """
    Image file size.

    @type: int
    """

    md5 = sqlalchemy.Column(sqlalchemy.String(32), nullable=False, unique=True,
                            index=True)
    """
    MD5 representation of image file.

    @type: str
    """

    def __init__(self, post=None):
        """
        Initialize Image.

        @param post: Post object for Image.
        @type post: model.Post or None

        @return: None
        @rtype: None
        """

        super(Image, self).__init__()

        self.status = self.STATUS_QUEUED

        if post:
            self.md5 = post.md5
            self.extension = post.extension
            self.size = post.size
            self.width = post.width
            self.height = post.height

    _str = None

    def __str__(self):
        """
        String representation of image data

        @rtype: str
        @return: Image information.
        """

        if self._str:
            return self._str

        notes = ['Dimension: %s x %s pixels.' % (self.width, self.height),
                 'Size: %s' % lib.component.Formatter.byte(self.size),
                 'MD5: %s' % self.md5, 'Type: %s' % self.extension]

        tag_names = [image_tag.name for image_tag in self.image_tags]
        notes.append('Tags: {0:s}'.format(' '.join(tag_names)))

        notes.append('')

        for image_host in self.image_hosts:
            notes.append('{0:s}:'.format(image_host.host.name))
            notes.append('    ID: {:,}'.format(image_host.original_id))

            pattern = '%A, %B %d, %Y %I:%M:%S %p'
            date_time = image_host.date_time.strftime(pattern)
            notes.append('    Uploaded: {0:s}'.format(date_time))

        self._str = '\n'.join(notes)

        return self._str

    def save_host(self, post):
        """
        Save new host information to ImageHost from post.

        @param post: Post object of the Image.
        @type post: model.Post or None

        @return: A boolean indicating whether operation succeed.
        @rtype: bool
        """

        session = sqlalchemy.inspect(self).session

        query = session.query(ImageHost)
        query = query.filter(ImageHost.image_id == self.id)
        query = query.filter(ImageHost.host_id == post.id)
        image_host = query.first()
        if image_host:
            return True

        image_host = ImageHost(post)
        image_host.image = self

        session.add(image_host)

        return True

    def save_tags(self, tags):
        """
        Save new tags to ImageTag and Tag.

        @param tags: A set of image tags to save.
        @type tags: set

        @return: A boolean indicating whether operation succeed.
        @rtype: bool
        """

        session = sqlalchemy.inspect(self).session

        existing_image_tag_names = []
        for image_tag in self.image_tags:
            existing_image_tag_names.append(image_tag.tag.name)

        query = session.query(Tag)
        query.filter(Tag.name.in_(tags))
        existing_tag_names = []
        existing_tags = {}
        for tag in query.all():
            existing_tag_names.append(tag.name)
            existing_tags[tag.name] = tag

        new_tag_names = tags - set(existing_tag_names)
        for new_tag in new_tag_names:
            tag = Tag(name=new_tag)
            session.add(tag)

            image_tag = ImageTag()
            image_tag.image = self
            image_tag.tag = tag
            session.add(image_tag)

        new_image_tag_names = tags - new_tag_names
        new_image_tag_names -= set(existing_image_tag_names)
        for new_image_tag_name in new_image_tag_names:
            image_tag = ImageTag()
            image_tag.image = self
            image_tag.tag = existing_tags[new_image_tag_name]
            session.add(image_tag)

        return True

    @property
    def filename(self):
        """
        A string of filename as md5.extension

        @return: A string of filename as md5.extension
        @rtype: str
        """

        return self.md5 + self.extension

    @property
    def human_filename(self):
        """
        A string of filename as tag.extension

        TODO:
            Search the tags and return that.
        """

        return self.md5 + self.extension

    _file = None

    @property
    def file(self):
        """A File instance of post"""

        if self._file:
            return self._file

        self._file = lib.component.File(os.path.join('images', self.filename))

        return self._file

    _temp = None

    @property
    def temp(self):
        """A File instance of temporary image"""

        if self._temp:
            return self._temp

        self._temp = lib.component.File(os.path.join('temps', self.filename))

        return self._temp

    def trash(self):
        """Set status to deleted and delete the file.

        Returns:
            Boolean indicating operation succeed. When operation fails, that
            because somehow python could not unlink the file.
        """

        self.status = self.STATUS_DELETED
        return self.file.unlink()

    def queue(self):
        """Set status to queued.

        Returns:
            Boolean.
        """

        self.status = self.STATUS_QUEUED
        return True

    def download(self):
        """Set status to downloaded.

        Returns:
            Boolean.
        """

        self.status = self.STATUS_DOWNLOADED
        return True

    def export(self, path, use_md5=False):
        """Save file to path.

        If use_md5 is false, will save as tag.extension, otherwise md5.extension
        and md5.txt which contains the tag.

        Args:
            path: A valid directory path.
            use_md5: Save file as md5.extension with text file containing the
                tag.

        Raises:
            See sh util copy error exception.
        """

        path = path if os.path.isabs(path) else os.path.abspath(path)
        destination = os.path.join(path, self.human_filename)
        if use_md5:
            destination = os.path.join(path, self.filename)

            md5_filename = self.md5 + '.txt'
            tag_file = open(os.path.join(path, md5_filename), 'w')
            tag_file.write('\n'.join(self.tags))
            tag_file.close()

        shutil.copy(self.file.absPath, destination)
